//TODO:add the windows XP view: icon+detail
#include <buola/gui.h>

#include <buola/image/ctextlayout.h>
#include <buola/image/cfont.h>
#include <buola/gui/cimagematrix.h>
#include <buola/gui/cconfig.h>
#include <buola/gui/cicon.h>
#include <buola/image/cimage.h>
#include <buola/image/cgc.h>
#include <buola/utility/cregion.h>

#include <buola/widgets/clistbox.h>
#include <buola/widgets/cscrollbar.h>
#include <buola/widgets/ceditbox.h>

#include "clbarrayrenderer.h"

namespace buola { namespace gui {

class CLBArrayRendererData
{
public:
    //preshape
    double mMinWidth;               //minimum width for the item
    CSize_d mIconSize;            

    //shape
    CSize_d mSize;                //actual size of the item
    CPoint_d mIconPos;            //position inside the whole item
    CPoint_d mTextPos;            //inside the item
    img::CTextLayout mTextLayout;

    //pos
    CPoint_d mPos;                //absolute position of the item
    int mRow;
};

CLBArrayRenderer::CLBArrayRenderer(CListBox *pListBox)
            :CLBRenderer(pListBox)
{
    mFont=img::CFont::GetStock(L"control");
    
    msg_info() << "font=" << (void*)&*mFont;

    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        InitItem(mListBox->GetItems()[i]);
    }

    CalcAll();
}

CLBArrayRenderer::~CLBArrayRenderer()
{
    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        DeinitItem(mListBox->GetItems()[i]);
    }
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
//////////////////////// Event handling ///////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
///////////////////// Internal functions //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CLBArrayRenderer::CalcAll()
{
    mIconSize=CSize_d(mListBox->GetIconSize());

    if(mListBox->GetStyle()&CListBox::STYLE_SINGLECOLUMN)
        mMinTextWidth=mListBox->Size().x;
    else
        mMinTextWidth=max(mIconSize.x*3,80.0);

    PreshapeAll();
    ShapeAll();
    CalcRows();
    PosAll();
}

void CLBArrayRenderer::InitItem(CListBox::SItem *pI)
{
    CLBArrayRendererData *lD=new CLBArrayRendererData;
    
    pI->mRendererData=lD;
}

void CLBArrayRenderer::DeinitItem(CListBox::SItem *pI)
{
    delete (CLBArrayRendererData*)pI->mRendererData;
    pI->mRendererData=nullptr;
}        

void CLBArrayRenderer::PreshapeItem(CListBox::SItem *pI)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;
    
    if(!pI->mImage)
    {
        lD->mIconSize=mIconSize;
    }
    else
    {
        lD->mIconSize=pI->mImage.Size();
    }

    lD->mMinWidth=max(lD->mIconSize.x,mMinTextWidth);

    if(lD->mMinWidth>mMaxMinWidth) 
    {   
        mMaxMinWidth=lD->mMinWidth;
    }
}

void CLBArrayRenderer::ShapeItem(CListBox::SItem *pI)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;
    
    CPoint_d lPos(0,0);

//Shape text
    lD->mTextLayout.SetDefaultFont(mFont);
    lD->mTextLayout.SetText(pI->mText);
    lD->mTextLayout.SetWidth(mMaxMinWidth);
    lD->mTextLayout.SetHeightLines(5);
    lD->mTextLayout.SetAlignMode(img::CTextLayout::ALIGN_CENTER);
    lD->mSize=lD->mTextLayout.GetSize()+CSize_d(6,6);
        
//Shape icon
    if(mListBox->GetStyle()&CListBox::STYLE_SINGLECOLUMN)
        lD->mSize.x=mMaxMinWidth;
    else
        lD->mSize.x=max(lD->mIconSize.x,lD->mSize.x);

    lD->mSize.y+=lD->mIconSize.y+4;

//Pos icon
    lD->mIconPos.x=lD->mSize.x/2-lD->mIconSize.x/2;
    lD->mIconPos.y=0;
            
//Pos text
    lD->mTextPos.y=lD->mIconSize.y+4;
    lD->mTextPos.x=lD->mSize.x/2-(lD->mTextLayout.GetSize().x+6)/2;
}

void CLBArrayRenderer::DrawItem(img::CGC *pGC,CListBox::SItem *pI)
{
    pGC->SetOperator(img::ECompOp::OVER);
    
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;
    
    CPoint_d lPos=lD->mPos-mListBox->GetScrollPos();

    if(pI->mSelected&&(mListBox->GetStyle()&CListBox::STYLE_SINGLECOLUMN))
    {
        pGC->SetSource(gConfig.mColors.Get(L"selection_back"));
        pGC->Path().Rect(lPos,lD->mSize);
        pGC->FillPath();
    }

    if(pI->mImage)
    {
        pGC->DrawImage(lPos+lD->mIconPos,pI->mImage);
    }
    else if(pI->mIcon)
    {
        pI->mIcon->Draw(pGC,lPos+lD->mIconPos,mListBox->GetIconSize(),pI->mSelected?CIcon::SELECTED:CIcon::NORMAL);
    }
    
    pGC->SetSource(gConfig.mColors.Get(pI->mSelected?L"selection_text":L"edit_text"));
    if(!pI->mSelected&&(mListBox->GetStyle()&CListBox::STYLE_INVERTTEXT))
    {
        pGC->SetSource({0.0,0.0,0.0});
        lD->mTextLayout.Draw(lPos+CPoint_d(lD->mSize.x/2-mMaxMinWidth/2,lD->mTextPos.y+3),pGC);
        pGC->SetSource({1.0,1.0,1.0});
        lD->mTextLayout.Draw(lPos+CPoint_d(lD->mSize.x/2-mMaxMinWidth/2-1,lD->mTextPos.y+2),pGC);
    }
    else
    {
        if(pI->mSelected)
        {
            pGC->SetSource(gConfig.mColors.Get(L"selection_back"));
            pGC->Path().RoundRect(CRect_d(lPos+lD->mTextPos,lD->mTextLayout.GetSize()+CSize_d(6,6)),6);
            pGC->FillPath();
            pGC->SetSource(gConfig.mColors.Get(L"selection_text"));
        }
        else
        {
            pGC->SetSource(gConfig.mColors.Get(L"edit_text"));
        }
    
        lD->mTextLayout.Draw(lPos+CPoint_d(lD->mSize.x/2-mMaxMinWidth/2,lD->mTextPos.y+3),pGC);
    }
}

void CLBArrayRenderer::RedrawItem(CListBox::SItem *pI,bool pRedraw)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;
    
    CRect_d lRect(lD->mPos-mListBox->GetScrollPos(),lD->mSize);

    img::CGC *lGC=mListBox->StartPainting(CRegion(lRect),pRedraw);
    mFont=img::CFont::GetStock(L"control");
    lGC->SetFont(mFont);
    DrawItem(lGC,pI);

    if(mListBox->mSelecting&&mListBox->mLastDragPoint!=mListBox->mDragPoint)
    {
        CRegion lInt(lRect);
        lInt&=CRect_d(mListBox->mLastDragPoint,mListBox->mDragPoint);

        lGC->SetOperator(img::ECompOp::OVER);
        lGC->SetSource(img::CColor(0.8,0.6,0.25,0.4));
        ///\todo lGC->FillRegion(lInt);
    }

    mListBox->EndPainting(lGC,CRegion(lRect));
}

int CLBArrayRenderer::HitTestItem(CListBox::SItem *pI,const CPoint_d &pP)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;
    
    CPoint_d lP=pP-lD->mPos+mListBox->GetScrollPos();
    
    if(lP.x<0||lP.y<0||lP.x>=lD->mSize.x||lP.y>=lD->mSize.y)
        return LBHT_NONE;
    
    if(CRect_d(lD->mIconPos,lD->mIconSize).Contains(lP))
        return LBHT_REGION|LBHT_ICON|LBHT_SELECT;
    
    if(CRect_d(lD->mTextPos,lD->mTextLayout.GetSize()+CSize_d(6,6)).Contains(lP))
        return LBHT_REGION|LBHT_TEXT|LBHT_SELECT;
    
    if(mListBox->GetStyle()&CListBox::STYLE_SINGLECOLUMN)
        return LBHT_REGION|LBHT_SELECT;
        
    return LBHT_REGION; 
}

int CLBArrayRenderer::HitTestItem(CListBox::SItem *pI,const CRect_d &pR)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;
    
    CRect_d lR=pR-lD->mPos+mListBox->GetScrollPos();
    
    if(lR.r<0||lR.b<0||lR.l>=lD->mSize.x||lR.t>=lD->mSize.y)
        return LBHT_NONE;
    
    if(lR.l<0&&lR.t<0&&lR.r>=lD->mSize.x&&lR.b>=lD->mSize.y)
        return LBHT_TOTAL;
    
    if(lR.r>=lD->mIconPos.x&&lR.b>=lD->mIconPos.y&&
            lR.l<lD->mIconPos.x+lD->mIconSize.x&&
            lR.t<lD->mIconPos.y+lD->mIconSize.y)
        return LBHT_PARTIAL;
    
    return LBHT_NONE;    
}

void CLBArrayRenderer::PreshapeAll()
{
    mMaxMinWidth=0;
    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        PreshapeItem(mListBox->GetItems()[i]);
    }
}

void CLBArrayRenderer::ShapeAll()
{
    for(int i=0;i<mListBox->GetItems().size();i++)
    {
        ShapeItem(mListBox->GetItems()[i]);
    }
}

void CLBArrayRenderer::CalcRows()
{
    CSize_d lSize=mListBox->Size();
    
    if(!mListBox->GetVScroll()->Mapped())
        lSize.x-=mListBox->GetVScroll()->Size().x;

    if(mListBox->GetStyle()&CListBox::STYLE_SINGLECOLUMN)
        mNumCols=1;
    else
        mNumCols=(int)((lSize.x)/(mMaxMinWidth+8));
    if(mNumCols<1) mNumCols=1;
        
    mNumRows=(mListBox->GetItemCount()+mNumCols-1)/mNumCols;
        
    mRowHeight.resize(mNumRows);
    mRowIconHeight.resize(mNumRows);
    mRowPos.resize(mNumRows);
    double lY=4+mListBox->GetMargins().t;
        
    for(int i=0;i<mNumRows;i++)
    {
        mRowHeight[i]=0;
        mRowIconHeight[i]=0;
        mRowPos[i]=lY;

        for(int j=0;j<mNumCols;j++)
        {
            int lNum=i*mNumCols+j;
            if(lNum>=mListBox->GetItemCount()) break;
                
            CListBox::SItem *lI=mListBox->GetItems()[lNum];
            CLBArrayRendererData *lD=(CLBArrayRendererData*)lI->mRendererData;

            if(lD->mSize.y>mRowHeight[i]) 
                                mRowHeight[i]=lD->mSize.y;
            if(lD->mIconSize.y>mRowIconHeight[i]) 
                                mRowIconHeight[i]=lD->mIconSize.y;
            lD->mRow=i;
        }           

        lY+=mRowHeight[i]+8;
    }
}

void CLBArrayRenderer::PosAll(int /*pFrom*/)
{
    CSize_d lSize=mListBox->Size();

    if(mListBox->GetStyle()&CListBox::STYLE_SINGLECOLUMN)
    {
        for(int i=0;i<mNumRows;i++)
        {
            CListBox::SItem *lI=mListBox->GetItems()[i];
            CLBArrayRendererData *lD=(CLBArrayRendererData*)lI->mRendererData;

            lD->mPos.x=0;
            lD->mPos.y=mRowPos[i]+(mRowIconHeight[i]-min(lD->mIconSize.y,
                              mRowHeight[i]-lD->mSize.y));
        }
    }
    else
    {
        for(int i=0;i<mNumRows;i++)
        {
            for(int j=0;j<mNumCols;j++)
            {
                int lNum=i*mNumCols+j;
                if(lNum>=mListBox->GetItemCount()) break;
                
                CListBox::SItem *lI=mListBox->GetItems()[lNum];
                CLBArrayRendererData *lD=(CLBArrayRendererData*)lI->mRendererData;

                lD->mPos.x=(mMaxMinWidth+8)*j+4+(mMaxMinWidth-lD->mSize.x)/2;
                lD->mPos.y=mRowPos[i]+min(mRowIconHeight[i]-lD->mIconSize.y,
                                  mRowHeight[i]-lD->mSize.y);
            }
        }
    }

    mTotalHeight=mNumRows?(mRowPos[mNumRows-1]+
                            mRowHeight[mNumRows-1]+4):0;

    int lScrollRange=(int)(mTotalHeight-lSize.y);

    if(lScrollRange<=0)
    {
        mListBox->GetVScroll()->SetRange(0,0);
        mListBox->GetVScroll()->Show(false);
    }
    else
    {
        mListBox->GetVScroll()->SetRange(0,lScrollRange);
        mListBox->GetVScroll()->SetStep((int)mRowHeight[0],(int)lSize.y);
        mListBox->GetVScroll()->Show(true);
    }

    mListBox->GetHScroll()->SetRange(0,0);
    mListBox->GetHScroll()->Show(false);
}

void CLBArrayRenderer::DrawAll(img::CGC *pGC)
{
    for(int i=0;i<mListBox->GetItemCount();i++)
    {
        CListBox::SItem *lI=mListBox->GetItems()[i];
        CLBArrayRendererData *lD=(CLBArrayRendererData*)lI->mRendererData;

        if(lD->mPos.x-mListBox->GetScrollPos().x>mListBox->Size().x+1||
           lD->mPos.x-mListBox->GetScrollPos().x+lD->mSize.x<0||
           lD->mPos.y-mListBox->GetScrollPos().y>mListBox->Size().y+1||
           lD->mPos.y-mListBox->GetScrollPos().y+lD->mSize.y<0) continue;
        
        DrawItem(pGC,lI);
    }
}

void CLBArrayRenderer::DrawAll(img::CGC *pGC,const CRect_d &pRect)
{
    for(int i=0;i<mListBox->GetItemCount();i++)
    {
        CListBox::SItem *lI=mListBox->GetItems()[i];
        CLBArrayRendererData *lD=(CLBArrayRendererData*)lI->mRendererData;

        if(lD->mPos.x-mListBox->GetScrollPos().x>pRect.r+1||
           lD->mPos.x-mListBox->GetScrollPos().x+lD->mSize.x<pRect.l||
           lD->mPos.y-mListBox->GetScrollPos().y>pRect.b+1||
           lD->mPos.y-mListBox->GetScrollPos().y+lD->mSize.y<pRect.t) continue;
        
        DrawItem(pGC,lI);
    }
}

///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
/////////////////////// Event handlers ////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////

void CLBArrayRenderer::OnSize(const CGeometryEvent &pE)
{
    CalcAll();
}

void CLBArrayRenderer::OnExpose(const CExposeEvent &pE)
{
    img::CGC *lGC=pE.mGC;
    
    lGC->SetSource(gConfig.mColors.Get(L"edit_text"));
    lGC->SetFont(mFont);
    DrawAll(lGC,pE.mRect);
}    

void CLBArrayRenderer::OnBigChange()
{
    CalcAll();
    mListBox->Refresh();
}

void CLBArrayRenderer::OnItemSelected(CListBox::SItem *pItem)
{
    RedrawItem(pItem,true);
}

void CLBArrayRenderer::OnItemAdded(CListBox::SItem *pItem)
{
    InitItem(pItem);
    double lOldMaxMinWidth=mMaxMinWidth;
    PreshapeItem(pItem);
    
    if(lOldMaxMinWidth!=mMaxMinWidth) //we must redo everything
    {
        ShapeAll();

        if(mListBox->GetAutoUpdate())
        {
            CalcRows();
            PosAll();
            mListBox->Refresh();
        }
        return;
    }

    ShapeItem(pItem);

    if(mListBox->GetAutoUpdate())
    {
        CalcRows();
        PosAll();
        RedrawItem(pItem,false);
    }
}

void CLBArrayRenderer::OnItemChanged(CListBox::SItem *pItem)
{
    ShapeItem(pItem);

    if(mListBox->GetAutoUpdate())
    {
        CalcRows();
        PosAll();
        RedrawItem(pItem,true);
    }
}

void CLBArrayRenderer::OnItemRemoved(CListBox::SItem *pI)
{
    DeinitItem(pI);
}

PEditBox CLBArrayRenderer::Edit(CListBox::SItem *pI)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;

    PEditBox lEditBox=new CEditBox;
    CPoint_d lPos=lD->mPos-mListBox->GetScrollPos();
    lEditBox->MoveResize(lPos+CPoint_d(lD->mSize.x/2-(mMaxMinWidth+6)/2,lD->mTextPos.y),
                CSize_d(mMaxMinWidth+6,lD->mTextLayout.GetSize().y+6));
    lEditBox->SetCaption(pI->mText);
    lEditBox->SetStyleBit(CWidget::STYLE_NOBORDER|CEditBox::STYLE_MULTILINE|
                                CWidget::STYLE_EDITOR|CEditBox::STYLE_CENTER);
    lEditBox->SelectAll();
    mListBox->Place(lEditBox);
    lEditBox->SetFocus();

    return lEditBox;
}

CPoint_d CLBArrayRenderer::GetIconPos(CListBox::SItem *pI)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;

    return lD->mPos+lD->mIconPos-mListBox->GetScrollPos();
}

CRect_d CLBArrayRenderer::GetItemRect(CListBox::SItem *pI)
{
    CLBArrayRendererData *lD=(CLBArrayRendererData*)pI->mRendererData;

    return CRect_d(lD->mPos-mListBox->GetScrollPos(),lD->mSize);
}

/*namespace gui*/ } /*namespace buola*/ }
